#include "uefi_helper.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <uefi.h>

EFI_GUID gEfiFileInfoGuid = EFI_FILE_INFO_ID;
EFI_GUID LoadedImageProtocol = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_GUID gLoadedImageProtocol = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_GUID gEfiGraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
EFI_GUID gEfiSimpleFileSystemProtocolGuid = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
EFI_GUID gEfiSerialIoProtocolGuid = EFI_SERIAL_IO_PROTOCOL_GUID;
EFI_GUID gLoadedImageProtocolGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_GUID gEfiLoadedImageProtocolGuid = EFI_LOADED_IMAGE_PROTOCOL_GUID;
EFI_GUID gEfiAcpiTableGuid = EFI_ACPI_TABLE_GUID;

EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *GOut;
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *GStderr;
EFI_HANDLE GImagehandle;
EFI_SYSTEM_TABLE *ST;
EFI_BOOT_SERVICES *BS;
EFI_RUNTIME_SERVICES *RT;

static bool helper_inited = false;

typedef enum format_type_width
{
    FMT_TYPE_CHAR,
    FMT_TYPE_SHORT,
    FMT_TYPE_INT,
    FMT_TYPE_LONG,
    FMT_TYPE_LONG_LONG,
    FMT_TYPE_SIZE,
    FMT_TYPE_PTRDIFF,
    FMT_TYPE_NONE,
} format_type_width;

typedef enum format_type
{
    FMT_SCHAR,
    FMT_SHORT_INT,
    FMT_INT,
    FMT_LONG_INT,
    FMT_LONG_LONG_INT,
    FMT_UCHAR,
    FMT_SHORT_UINT,
    FMT_UINT,
    FMT_LONG_UINT,
    FMT_LONG_LONG_UINT,
    FMT_SIZE,
    FMT_PTR,
    FMT_PTRDIFF,
    FMT_STR,
    FMT_UEFI_STR,
    FMT_PERCENT,
    FMT_CHAR,
    FMT_NONE,
} format_type;

const unsigned FMT_FLAG_MINUS = 1;
const unsigned FMT_FLAG_PLUS = 2;
const unsigned FMT_FLAG_SPACE = 4;
const unsigned FMT_FLAG_BASE = 8;
const unsigned FMT_FLAG_ZERO = 16;

typedef struct format
{
    unsigned flags;
    bool width_from_args;
    unsigned width;
    enum format_type_width type_width;
    enum format_type type;
    unsigned base;
    size_t size;
} format;

typedef struct format_buffer
{
    uint16_t *buffer;
    size_t size;
    size_t count;
} format_buffer;

typedef struct Efi_errors
{
    EFI_STATUS code;
    uint16_t *desc;
} Efi_errors;
Efi_errors Efi_error_info[] = {{EFI_SUCCESS, L"Success"},
                               {EFI_LOAD_ERROR, L"Load Error"},
                               {EFI_INVALID_PARAMETER, L"Invalid Parameter"},
                               {EFI_UNSUPPORTED, L"Unsupported"},
                               {EFI_BAD_BUFFER_SIZE, L"Bad Buffer Size"},
                               {EFI_BUFFER_TOO_SMALL, L"Buffer Too Small"},
                               {EFI_NOT_READY, L"Not Ready"},
                               {EFI_DEVICE_ERROR, L"Device Error"},
                               {EFI_WRITE_PROTECTED, L"Write Protected"},
                               {EFI_OUT_OF_RESOURCES, L"Out of Resources"},
                               {EFI_VOLUME_CORRUPTED, L"Volume Corrupt"},
                               {EFI_VOLUME_FULL, L"Volume Full"},
                               {EFI_NO_MEDIA, L"No Media"},
                               {EFI_MEDIA_CHANGED, L"Media changed"},
                               {EFI_NOT_FOUND, L"Not Found"},
                               {EFI_ACCESS_DENIED, L"Access Denied"},
                               {EFI_NO_RESPONSE, L"No Response"},
                               {EFI_NO_MAPPING, L"No mapping"},
                               {EFI_TIMEOUT, L"Time out"},
                               {EFI_NOT_STARTED, L"Not started"},
                               {EFI_ALREADY_STARTED, L"Already started"},
                               {EFI_ABORTED, L"Aborted"},
                               {EFI_ICMP_ERROR, L"ICMP Error"},
                               {EFI_TFTP_ERROR, L"TFTP Error"},
                               {EFI_PROTOCOL_ERROR, L"Protocol Error"},
                               {EFI_INCOMPATIBLE_VERSION, L"Incompatible Version"},
                               {EFI_SECURITY_VIOLATION, L"Security Policy Violation"},
                               {EFI_CRC_ERROR, L"CRC Error"},
                               {EFI_END_OF_MEDIA, L"End of Media"},
                               {EFI_END_OF_FILE, L"End of File"},
                               {EFI_INVALID_LANGUAGE, L"Invalid Languages"},
                               {EFI_COMPROMISED_DATA, L"Compromised Data"},

                               // warnings
                               {EFI_WARN_UNKNOWN_GLYPH, L"Warning Unknown Glyph"},
                               {EFI_WARN_DELETE_FAILURE, L"Warning Delete Failure"},
                               {EFI_WARN_WRITE_FAILURE, L"Warning Write Failure"},
                               {EFI_WARN_BUFFER_TOO_SMALL, L"Warning Buffer Too Small"},
                               {0, NULL}};

EFI_STATUS init_helper(EFI_HANDLE image_handle, EFI_SYSTEM_TABLE *system_table)
{
    EFI_LOADED_IMAGE *loaded_image;
    EFI_STATUS status = EFI_SUCCESS;

    if (!helper_inited)
    {
        helper_inited = true;
        GImagehandle = image_handle;

        // Set up global pointer to the system table, boot services table,
        // and runtime services table
        ST = system_table;
        BS = system_table->BootServices;
        RT = system_table->RuntimeServices;
        GOut = ST->ConOut;
        GStderr = ST->StdErr;

        if (image_handle)
        {
            status = BS->HandleProtocol(image_handle, &LoadedImageProtocol, (VOID *)&loaded_image);
        }
    }
    return status;
}

int64_t CompareGuid(EFI_GUID *Guid1, EFI_GUID *Guid2)
{
    INT32 *g1;
    INT32 *g2;
    INT32 r;
    // Compare 32 bits at a time
    g1 = (INT32 *)Guid1;
    g2 = (INT32 *)Guid2;

    r = g1[0] - g2[0];
    r |= g1[1] - g2[1];
    r |= g1[2] - g2[2];
    r |= g1[3] - g2[3];

    return r;
}

static void buffer_append(format_buffer *buffer, int code)
{
    if (buffer->count < buffer->size)
        buffer->buffer[buffer->count] = code;
    ++buffer->count;
}

static void buffer_copy(format_buffer *buffer, const char *begin, const char *end)
{
    const size_t len = end - begin;
    const size_t rem = buffer->count < buffer->size ? buffer->size - buffer->count : 0;
    const size_t copy = len < rem ? len : rem;
    uint16_t *pos = buffer->buffer + buffer->count;

    for (size_t i = 0; i < copy; i++)
        *pos++ = begin[i];
    buffer->count += len;
}

static void buffer_copyU(format_buffer *buffer, const uint16_t *begin, const uint16_t *end)
{
    const size_t len = end - begin;
    const size_t rem = buffer->count < buffer->size ? buffer->size - buffer->count : 0;
    const size_t copy = len < rem ? len : rem;
    uint16_t *pos = buffer->buffer + buffer->count;

    for (size_t i = 0; i < copy; i++)
        *pos++ = begin[i];
    buffer->count += len;
}

static void buffer_format_unsigned(format_buffer *buffer, format *config, unsigned long long v)
{
    char buf[32];
    char *pos = buf;

    do
    {
        const unsigned d = v % config->base;

        v = v / config->base;
        if (d < 10)
            *pos = d + '0';
        else
            *pos = d - 10 + 'A';
        ++pos;
    } while (v);

    for (char *l = buf, *r = pos - 1; l < r; ++l, --r)
    {
        const char c = *l;

        *l = *r;
        *r = c;
    }

    buffer_copy(buffer, buf, pos);
}

static void buffer_format_signed(format_buffer *buffer, format *config, long long v)
{
    if (v < 0)
    {
        buffer_append(buffer, '-');
        v = -v;
    }
    buffer_format_unsigned(buffer, config, v);
}

static const char *format_parse_flags(const char *str, format *config)
{
    while (*str)
    {
        bool is_flag = true;

        switch (*str)
        {
        case '-':
            config->flags = FMT_FLAG_MINUS;
            break;
        case '+':
            config->flags = FMT_FLAG_PLUS;
            break;
        case ' ':
            config->flags = FMT_FLAG_SPACE;
            break;
        case '#':
            config->flags = FMT_FLAG_BASE;
            break;
        case '0':
            config->flags = FMT_FLAG_ZERO;
            break;
        default:
            is_flag = false;
            break;
        }

        if (!is_flag)
            break;
        ++str;
    }
    return str;
}

static const char *format_parse_width(const char *str, format *config)
{
    if (*str == '*')
    {
        config->width_from_args = true;
        return str + 1;
    }

    while ('0' <= *str && *str <= '9')
    {
        config->width = config->width * 10 + (*str - '0');
        ++str;
    }

    return str;
}

static const char *format_parse_type_width(const char *str, format *config)
{
    if (*str == 'z')
    {
        config->type_width = FMT_TYPE_SIZE;
        return str + 1;
    }

    if (*str == 't')
    {
        config->type_width = FMT_TYPE_PTRDIFF;
        return str + 1;
    }

    if (*str == 'h')
    {
        ++str;
        if (*str == 'h')
        {
            config->type_width = FMT_TYPE_CHAR;
            return str + 1;
        }
        config->type_width = FMT_TYPE_SHORT;
        return str;
    }

    if (*str == 'l')
    {
        ++str;
        if (*str == 'l')
        {
            config->type_width = FMT_TYPE_LONG_LONG;
            return str + 1;
        }
        config->type_width = FMT_TYPE_LONG;
        return str;
    }

    return str;
}

static format_type format_adjusted_type(format_type type, format_type_width width)
{
    if (type == FMT_INT)
    {
        switch (width)
        {
        case FMT_TYPE_CHAR:
            return FMT_SCHAR;
        case FMT_TYPE_SHORT:
            return FMT_SHORT_INT;
        case FMT_TYPE_LONG:
            return FMT_LONG_INT;
        case FMT_TYPE_LONG_LONG:
            return FMT_LONG_LONG_INT;
        case FMT_TYPE_SIZE:
            return FMT_SIZE;
        case FMT_TYPE_PTRDIFF:
            return FMT_PTRDIFF;
        default:
            return type;
        }
    }

    if (type == FMT_UINT)
    {
        switch (width)
        {
        case FMT_TYPE_CHAR:
            return FMT_UCHAR;
        case FMT_TYPE_SHORT:
            return FMT_SHORT_UINT;
        case FMT_TYPE_LONG:
            return FMT_LONG_UINT;
        case FMT_TYPE_LONG_LONG:
            return FMT_LONG_LONG_UINT;
        case FMT_TYPE_SIZE:
            return FMT_SIZE;
        case FMT_TYPE_PTRDIFF:
            return FMT_PTRDIFF;
        default:
            return type;
        }
    }
    return type;
}

static const char *format_parse_spec(const char *str, format *config)
{
    const char *pos = str;
    if (*pos == '%')
    {
        config->type = FMT_PERCENT;
        return pos + 1;
    }

    pos = format_parse_type_width(pos, config);

    switch (*pos)
    {
    case 'd':
    case 'i':
    case 'u':
        config->base = 10;
        break;
    case 'o':
        config->base = 8;
        break;
    case 'x':
    case 'X':
    case 'p':
        config->base = 16;
        break;
    }

    switch (*pos)
    {
    case 'd':
    case 'i':
        config->type = format_adjusted_type(FMT_INT, config->type_width);
        break;
    case 'u':
    case 'o':
    case 'x':
    case 'X':
        config->type = format_adjusted_type(FMT_UINT, config->type_width);
        break;
    case 'c':
        config->type = FMT_CHAR;
        break;
    case 's':
        config->type = FMT_STR;
        break;
    case 'w':
        config->type = FMT_UEFI_STR;
        break;
    case 'p':
        config->type = FMT_PTR;
        break;
    default:
        return str;
    }

    return pos + 1;
}

static int format_parse(const char *str, format *config)
{
    const char *pos = str;
    const char *end;

    config->flags = 0;
    config->width_from_args = false;
    config->width = 0;
    config->type_width = FMT_TYPE_NONE;
    config->type = FMT_NONE;
    config->base = 0;
    config->size = 0;

    pos = format_parse_flags(pos, config);
    pos = format_parse_width(pos, config);

    // The spec is the only mandatory part, so if it's empty it's a problem
    end = format_parse_spec(pos, config);
    if (pos == end)
        return -1;
    config->size = end - str;

    return 0;
}

int vsnprintf(uint16_t *buffer, size_t size, const char *fmt, va_list args)
{
    struct format_buffer buf = {buffer, size - 1, 0};
    const char *current = fmt;

    while (*current)
    {
        const char *start = current;
        const char *end = current;

        struct format config;
        unsigned long long uv;
        long long v;
        int ret;

        while (*end && *end != '%')
            ++end;

        buffer_copy(&buf, start, end);
        if (*end == '\0')
        {
            current = end;
            break;
        }

        ret = format_parse(end + 1, &config);
        if (ret < 0)
            return ret;
        current = end + 1 + config.size;

        if (config.width_from_args)
            config.width = (unsigned)va_arg(args, int);

        switch (config.type)
        {
        case FMT_STR: {
            const char *str = va_arg(args, const char *);
            buffer_copy(&buf, str, str + strlen(str));
            break;
        }
        case FMT_UEFI_STR: {
            const uint16_t *str = va_arg(args, const uint16_t *);
            buffer_copyU(&buf, str, str + wcslen(str));
            break;
        }
        case FMT_CHAR: {
            int v = va_arg(args, int);
            buffer_append(&buf, v);
            break;
        }
        case FMT_PERCENT:
            buffer_append(&buf, u'%');
            break;
        case FMT_SCHAR:
        case FMT_SHORT_INT:
        case FMT_INT:
            v = va_arg(args, int);
            buffer_format_signed(&buf, &config, v);
            break;
        case FMT_LONG_INT:
            v = va_arg(args, long);
            buffer_format_signed(&buf, &config, v);
            break;
        case FMT_LONG_LONG_INT:
            v = va_arg(args, long long);
            buffer_format_signed(&buf, &config, v);
            break;
        case FMT_PTRDIFF:
            v = (long long)va_arg(args, ptrdiff_t);
            buffer_format_unsigned(&buf, &config, v);
            break;
        case FMT_UCHAR:
        case FMT_SHORT_UINT:
        case FMT_UINT:
            uv = va_arg(args, unsigned);
            buffer_format_unsigned(&buf, &config, uv);
            break;
        case FMT_LONG_UINT:
            uv = va_arg(args, unsigned long);
            buffer_format_unsigned(&buf, &config, uv);
            break;
        case FMT_LONG_LONG_UINT:
            uv = va_arg(args, unsigned long long);
            buffer_format_unsigned(&buf, &config, uv);
            break;
        case FMT_SIZE:
            uv = va_arg(args, size_t);
            buffer_format_unsigned(&buf, &config, uv);
            break;
        case FMT_PTR:
            uv = (unsigned long long)va_arg(args, void *);
            buffer_format_unsigned(&buf, &config, uv);
            break;
        default:
            return -1;
        }
    }

    if (buf.count < buf.size)
        buf.buffer[buf.count] = u'\0';
    else
        buf.buffer[buf.size] = u'\0';

    return (int)buf.count;
}

static void buffer_format_unsignedU(format_buffer *buffer, format *config, unsigned long long v)
{
    CHAR16 buf[32];
    CHAR16 *pos = buf;

    do
    {
        const unsigned d = v % config->base;

        v = v / config->base;
        if (d < 10)
            *pos = d + '0';
        else
            *pos = d - 10 + 'A';
        ++pos;
    } while (v);

    for (CHAR16 *l = buf, *r = pos - 1; l < r; ++l, --r)
    {
        const CHAR16 c = *l;

        *l = *r;
        *r = c;
    }

    buffer_copyU(buffer, buf, pos);
}

static const CHAR16 *format_parse_flagsU(const CHAR16 *str, format *config)
{
    while (*str)
    {
        bool is_flag = true;

        switch (*str)
        {
        case '-':
            config->flags = FMT_FLAG_MINUS;
            break;
        case '+':
            config->flags = FMT_FLAG_PLUS;
            break;
        case ' ':
            config->flags = FMT_FLAG_SPACE;
            break;
        case '#':
            config->flags = FMT_FLAG_BASE;
            break;
        case '0':
            config->flags = FMT_FLAG_ZERO;
            break;
        default:
            is_flag = false;
            break;
        }

        if (!is_flag)
            break;
        ++str;
    }
    return str;
}

static const CHAR16 *format_parse_widthU(const CHAR16 *str, format *config)
{
    if (*str == '*')
    {
        config->width_from_args = true;
        return str + 1;
    }

    while ('0' <= *str && *str <= '9')
    {
        config->width = config->width * 10 + (*str - '0');
        ++str;
    }

    return str;
}

static const CHAR16 *format_parse_type_widthU(const CHAR16 *str, format *config)
{
    if (*str == 'z')
    {
        config->type_width = FMT_TYPE_SIZE;
        return str + 1;
    }

    if (*str == 't')
    {
        config->type_width = FMT_TYPE_PTRDIFF;
        return str + 1;
    }

    if (*str == 'h')
    {
        ++str;
        if (*str == 'h')
        {
            config->type_width = FMT_TYPE_CHAR;
            return str + 1;
        }
        config->type_width = FMT_TYPE_SHORT;
        return str;
    }

    if (*str == 'l')
    {
        ++str;
        if (*str == 'l')
        {
            config->type_width = FMT_TYPE_LONG_LONG;
            return str + 1;
        }
        config->type_width = FMT_TYPE_LONG;
        return str;
    }

    return str;
}

static const CHAR16 *format_parse_specU(const CHAR16 *str, format *config)
{
    const CHAR16 *pos = str;

    if (*pos == '%')
    {
        config->type = FMT_PERCENT;
        return pos + 1;
    }

    pos = format_parse_type_widthU(pos, config);

    switch (*pos)
    {
    case 'd':
    case 'i':
    case 'u':
        config->base = 10;
        break;
    case 'o':
        config->base = 8;
        break;
    case 'x':
    case 'X':
    case 'p':
        config->base = 16;
        break;
    }

    switch (*pos)
    {
    case 'd':
    case 'i':
        config->type = format_adjusted_type(FMT_INT, config->type_width);
        break;
    case 'u':
    case 'o':
    case 'x':
    case 'X':
        config->type = format_adjusted_type(FMT_UINT, config->type_width);
        break;
    case 'c':
        config->type = FMT_CHAR;
        break;
    case 's':
        config->type = FMT_STR;
        break;
    case 'w':
        config->type = FMT_UEFI_STR;
        break;
    case 'p':
        config->type = FMT_PTR;
        break;
    default:
        return str;
    }

    return pos + 1;
}

static int format_parseU(const CHAR16 *str, format *config)
{
    const CHAR16 *pos = str;
    const CHAR16 *end;

    config->flags = 0;
    config->width_from_args = false;
    config->width = 0;
    config->type_width = FMT_TYPE_NONE;
    config->type = FMT_NONE;
    config->base = 0;
    config->size = 0;

    pos = format_parse_flagsU(pos, config);
    pos = format_parse_widthU(pos, config);

    // The spec is the only mandatory part, so if it's empty it's a problem
    end = format_parse_specU(pos, config);
    if (pos == end)
        return -1;
    config->size = end - str;

    return 0;
}

int vswnprintf(uint16_t *buffer, size_t size, const uint16_t *fmt, va_list args)
{
    struct format_buffer buf = {buffer, size - 1, 0};
    const CHAR16 *current = fmt;

    while (*current)
    {
        const CHAR16 *start = current;
        const CHAR16 *end = current;

        struct format config;
        unsigned long long uv;
        long long v;
        int ret;

        while (*end && *end != '%')
            ++end;

        buffer_copyU(&buf, start, end);
        if (*end == '\0')
        {
            current = end;
            break;
        }

        ret = format_parseU(end + 1, &config);
        if (ret < 0)
            return ret;
        current = end + 1 + config.size;

        if (config.width_from_args)
            config.width = (unsigned)va_arg(args, int);

        switch (config.type)
        {
        case FMT_STR: {
            const CHAR16 *str = va_arg(args, const CHAR16 *);
            buffer_copyU(&buf, str, str + wcslen(str));
            break;
        }
        case FMT_UEFI_STR: {
            const uint16_t *str = va_arg(args, const uint16_t *);
            buffer_copyU(&buf, str, str + wcslen(str));
            break;
        }
        case FMT_CHAR: {
            int v = va_arg(args, int);
            buffer_append(&buf, v);
            break;
        }
        case FMT_PERCENT:
            buffer_append(&buf, u'%');
            break;
        case FMT_SCHAR:
        case FMT_SHORT_INT:
        case FMT_INT:
            v = va_arg(args, int);
            buffer_format_signed(&buf, &config, v);
            break;
        case FMT_LONG_INT:
            v = va_arg(args, long);
            buffer_format_signed(&buf, &config, v);
            break;
        case FMT_LONG_LONG_INT:
            v = va_arg(args, long long);
            buffer_format_signed(&buf, &config, v);
            break;
        case FMT_PTRDIFF:
            v = (long long)va_arg(args, ptrdiff_t);
            buffer_format_unsignedU(&buf, &config, v);
            break;
        case FMT_UCHAR:
        case FMT_SHORT_UINT:
        case FMT_UINT:
            uv = va_arg(args, unsigned);
            buffer_format_unsignedU(&buf, &config, uv);
            break;
        case FMT_LONG_UINT:
            uv = va_arg(args, unsigned long);
            buffer_format_unsignedU(&buf, &config, uv);
            break;
        case FMT_LONG_LONG_UINT:
            uv = va_arg(args, unsigned long long);
            buffer_format_unsignedU(&buf, &config, uv);
            break;
        case FMT_SIZE:
            uv = va_arg(args, size_t);
            buffer_format_unsignedU(&buf, &config, uv);
            break;
        case FMT_PTR:
            uv = (unsigned long long)va_arg(args, void *);
            buffer_format_unsignedU(&buf, &config, uv);
            break;
        default:
            return -1;
        }
    }

    if (buf.count < buf.size)
        buf.buffer[buf.count] = u'\0';
    else
        buf.buffer[buf.size] = u'\0';

    return (int)buf.count;
}

int vswprintf(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *out, const uint16_t *fmt, va_list args)
{
    uint16_t msg[512];
    int res = 0;
    res = vswnprintf(msg, sizeof(msg), fmt, args);

    out->OutputString(out, msg);
    return res;
}

void FPrint(UEFI_STREAN *out, const uint16_t *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vswprintf(out, fmt, args);
    va_end(args);
}

void EPrint(const uint16_t *fmt, ...)
{
#ifdef DEBUG
    va_list args;

    va_start(args, fmt);
    vswprintf(GStderr, fmt, args);
    va_end(args);
#endif
}

void EPrintln(const uint16_t *fmt, ...)
{
#ifdef DEBUG
    va_list args;

    va_start(args, fmt);
    vswprintf(GStderr, fmt, args);
    va_end(args);
    EPrint(L"\r\n");
#endif
}

void Print(const uint16_t *fmt, ...)
{
#ifdef DEBUG
    va_list args;

    va_start(args, fmt);
    vswprintf(GOut, fmt, args);
    va_end(args);
#endif
}

void Println(const uint16_t *fmt, ...)
{
#ifdef DEBUG
    va_list args;

    va_start(args, fmt);
    vswprintf(GOut, fmt, args);
    va_end(args);
    Print(L"\r\n");
#endif
}

static void vsprintf(EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *out, const char *fmt, va_list args)
{
    uint16_t msg[512];

    vsnprintf(msg, sizeof(msg), fmt, args);

    out->OutputString(out, msg);
}

void fprint(UEFI_STREAN *out, const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vsprintf(out, fmt, args);
    va_end(args);
}

void eprint(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vsprintf(GStderr, fmt, args);
    va_end(args);
}

void eprintln(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vsprintf(GStderr, fmt, args);
    va_end(args);
    eprint("\r\n");
}

void print(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vsprintf(GOut, fmt, args);
    va_end(args);
}

void println(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vsprintf(GStderr, fmt, args);
    va_end(args);
    print("\r\n");
}

EFI_STATUS StatusOut(EFI_STATUS status)
{
    for (size_t i = 0; i < 37; i++)
    {
        if (Efi_error_info[i].code == status)
        {
            EPrint(Efi_error_info[i].desc);
            EPrintln(L". code: %X", status);
            return status;
        }
    }
    EPrint(L"Unknown error");
    EPrintln(L". code: %X", status);
    return status;
}

void CleanScreen(){
    GOut->ClearScreen(GOut);
}

/*
 ***********************************************************
 *
 *             uefi helper-lib
 *
 * *********************************************************
 */

size_t strlen(const char *str)
{
    const char *pos = str;

    while (*pos++)
        ;
    return pos - str - 1;
}

size_t wcslen(const uint16_t *str)
{
    const uint16_t *pos = str;

    while (*pos++)
        ;
    return pos - str - 1;
}

int isdigit(int code)
{
    return code >= '0' && code <= '9';
}

int isalpha(int code)
{
    return (code >= 'a' && code <= 'z') || (code >= 'A' && code <= 'Z');
}

int isalnum(int code)
{
    return isdigit(code) || isalpha(code);
}

int is_space(int code)
{
    return code == ' ' || code == '\r' || code == '\n' || code == '\t';
}

void *memset(void *ptr, int value, size_t size)
{
    char *to = ptr;

    for (size_t i = 0; i < size; i++)
        *to++ = value;

    return ptr;
}

void *memcpy(void *dst, const void *src, size_t size)
{
    const char *from = src;
    char *to = dst;

    for (size_t i = 0; i < size; i++)
        *to++ = *from++;

    return dst;
}

char *strncpy(char *dst, const char *src, size_t size)
{
    size_t i = 0;

    for (; i < size && src[i]; i++)
        dst[i] = src[i];

    for (; i < size; i++)
        dst[i] = '\0';

    return dst;
}

uint16_t *wcsncpy(uint16_t *dst, const char *src, size_t size)
{
    size_t i = 0;

    for (; i < size && src[i]; i++)
        dst[i] = src[i];

    for (; i < size; i++)
        dst[i] = '\0';

    return dst;
}

int strcmp(const char *l, const char *r)
{
    while (*l == *r && *l != '\0')
    {
        ++l;
        ++r;
    }

    if (*l == *r)
        return 0;

    if (*l < *r)
        return -1;
    else
        return 1;
}
